import { INS } from './Public.js';
import Node from './Node.js';
import parse from './path-parse.js';

import { get, setWith, memoize, cloneDeep, unset, values } from 'lodash-es';
const PathStringToPosition = memoize(function (PathString) {
    // console.log(PathString);
    const parsed = parse(PathString);
    return {
        ...parsed,
        pos: [...parsed.dir.split(/\/|\\/), parsed.name].filter((i) => i && i !== ''),
    };
});
export default class Structure {
    root = '/';
    tree = {};
    constructor(name) {
        this.INS = INS;
    }
    async stat(path) {
        const { pos } = PathStringToPosition(path);
        const target = get(this.tree, pos);
        return target ? target[INS] : false;
    }
    async readdir(path, options = { withFileTypes: false }) {
        const { pos } = PathStringToPosition(path);
        const target = get(this.tree, pos);
        if (target?.[INS].type === 'directory') {
            return values(target).map((i) => {
                const stat = i[INS];
                return options.withFileTypes ? stat : stat.name + stat.ext;
            });
        } else {
            throw new Error('file System readFile: This path is not a File');
        }
    }
    async readFile(path, options) {
        const { pos } = PathStringToPosition(path);
        const target = get(this.tree, pos);
        if (target[INS].type === 'file') {
            return target[INS].data;
        } else {
            throw new Error('file System readFile: This path is not a File');
        }
    }
    // 文件夹只是虚拟设置，如果没有填入文件是不会有任何实际反应的
    pathExists(dir) {
        const { pos } = PathStringToPosition(dir);
        const target = get(this.tree, pos);
        return target || false;
    }
    async ensureDir(dir) {
        return this._ensure('directory', dir);
    }
    async ensureFile(dir) {
        return this._ensure('file', dir);
    }
    async _ensure(type, dir) {
        const { pos } = PathStringToPosition(dir);
        const target = get(this.tree, pos);
        if (target && target[INS]) {
            if (target[INS].type === type) {
                return target;
            } else {
                throw new Error(`fileSystem ensure: Your Path is not a  ${type}, your Path is a ${target.type}`);
            }
        } else {
            const created = new Node({
                type,
                name: pos[pos.length - 1],
            });
            return this._write(pos, created);
        }
    }
    _write(dir, created) {
        let pos = typeof dir === 'string' ? PathStringToPosition(dir).pos : dir;
        setWith(this.tree, pos, created, (oldVal, name, _) => {
            if (oldVal instanceof Node) {
                if (oldVal[INS].type !== 'directory') {
                    throw new Error('file System _write: You are writing a file as directory');
                } else {
                    return oldVal;
                }
            } else {
                return new Node({ name });
            }
        });
        return get(this.tree, pos);
    }
    async outputFile(dir, data, options = { flag: 'w' }) {
        const target = this.pathExists(dir);
        if (!target || target[INS]?.type === 'file') {
            const { pos, ext, name } = PathStringToPosition(dir);
            return this._write(
                pos,
                new Node({
                    name,
                    type: 'file',
                    ext,
                    time: new Date().getTime(),
                    size: -1,
                    data,
                }),
            );
        } else {
            throw new Error('file System outputFile: output path does not a file or clean path');
        }
    }
    outputJSON(dir, data, options = {}) {
        return this.outputFile(dir, JSON.stringify(data), options);
    }
    // copy 是深层复制，move 是改变位置
    async copy(src, dest, options) {
        const { overwrite = true, errorOnExist = false, filter = null } = options || {};

        if (overwrite || !this.pathExists(dest)) {
            // overwrite 被设定后，直接将其覆盖，不考虑存不存在
            // 如果不存在地址，也可以直接写入
            const { pos: posSrc } = PathStringToPosition(src);
            const { pos: posDest, ext } = PathStringToPosition(dest);
            const target = get(this.tree, posSrc);
            return this._write(
                posDest,
                this._cloneNode(target, {
                    name: posDest[posDest.length - 1],
                    ext,
                }),
            );
        } else {
            throw new Error('file System copy: Path Already Existed And |overwrite=false|');
        }
    }
    _cloneNode(target, template = {}) {
        const result = cloneDeep(target);
        result[INS] = { ...result[INS], ...template };
        return result;
    }
    async emptyDir(dir) {
        const { pos } = PathStringToPosition(dir);
        const target = get(this.tree, pos);
        if (target[INS].type === 'directory') {
            return this._write(pos, target);
        } else {
            throw new Error('file System emptyDir: This Path is not a Directory or not Exist');
        }
    }
    // 单个文件 move 到文件夹
    async move(src, dest, options) {
        const { overwrite = false } = options || {};
        if (overwrite || !this.pathExists(dest)) {
            // overwrite 被设定后，直接将其覆盖，不考虑存不存在
            // 如果不存在地址，也可以直接写入
            const { pos: posDest } = PathStringToPosition(dest);
            // console.log(posSrc, posDest);
            const target = await this.remove(src);
            return this._write(posDest, target);
        } else {
            throw new Error('file System copy: Path Already Existed And |overwrite=false|');
        }
    }
    async remove(dir) {
        const { pos } = PathStringToPosition(dir);
        const target = get(this.tree, pos);
        unset(this.tree, pos);
        return target;
    }
}
